package com.example.lottery.service.impl;

import com.example.lottery.common.errorcode.ServiceErrorCodeConstants;
import com.example.lottery.common.exception.ServiceException;
import com.example.lottery.common.utils.*;
import com.example.lottery.controller.param.UserLoginByMessageParam;
import com.example.lottery.controller.param.UserLoginByPasswordParam;
import com.example.lottery.controller.param.UserLoginParam;
import com.example.lottery.controller.param.UserRegisterParam;
import com.example.lottery.dao.dataobject.Encrypt;
import com.example.lottery.dao.dataobject.UserDO;
import com.example.lottery.dao.mapper.UserMapper;
import com.example.lottery.service.IUserService;
import com.example.lottery.service.IVerificationCodeService;
import com.example.lottery.service.dto.UserBaseInfoDTO;
import com.example.lottery.service.dto.UserLoginDTO;
import com.example.lottery.service.dto.UserRegisterDTO;
import com.example.lottery.service.enums.UserIdentityEnum;
import io.jsonwebtoken.Claims;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
public class UserServiceImpl implements IUserService {

    @Autowired
    private IVerificationCodeService verificationCodeService;
    @Autowired
    private JWTUtil jwtUtil;
    @Autowired
    private RedisUtil redisUtil;
@Autowired
private UserMapper userMapper;
    /**
     * 注册用户逻辑
     * @param userRegisterParam 添加用户信息
     * @return 用户的id主键
     */
    @Override
    public UserRegisterDTO userRegister(UserRegisterParam userRegisterParam) {
        //校验注册信息
        checkRegisterUserInfo(userRegisterParam);

        //构造User，插入
        UserDO userDO =new UserDO();
        //自动加密了
        userDO.setPhoneNumber(new Encrypt(userRegisterParam.getPhoneNumber()));
        userDO.setUserName(userRegisterParam.getUserName());
        userDO.setIdentity(userRegisterParam.getIdentity());
        userDO.setEmail(userRegisterParam.getEmail());
        Date date=new Date();
        userDO.setGmtCreate(date);
        userDO.setGmtModified(date);


        //密码SHA256摘要加密
        if(StringUtils.hasLength(userRegisterParam.getPassword())){
            Map<String, String> map = EncryptUtils.EncryptSHA256(userRegisterParam.getPassword());
            //将salt、密文存入数据库
            userDO.setPassword(map.get("secret"));
            userDO.setSalt(map.get("salt"));
        }

        //普通用户注册不需要设置密码和salt

        //插入用户信息
        int insert =userMapper.insert(userDO);

        //校验是否插入成功
        if(insert!=1){
            log.warn(ServiceErrorCodeConstants.USER_INSERT_ERROR.getMsg());
            throw new ServiceException(ServiceErrorCodeConstants.USER_INSERT_ERROR);
        }

        //返回userId值
        return new UserRegisterDTO(userDO.getId());
    }

    @Override
    public UserLoginDTO userLogin(UserLoginParam loginParam) {
        //1-    区分是User loginByPasswordParam  or User loginByMessageParam
        UserLoginDTO loginDTO;
        if(loginParam instanceof UserLoginByPasswordParam){
            //是UserLoginByPasswordParam的实例，开始强制转换
            UserLoginByPasswordParam userLoginByPasswordParam=(UserLoginByPasswordParam)loginParam;
            //2-    执行密码登陆逻辑
            loginDTO=userLoginByPassword(userLoginByPasswordParam);
        }else if(loginParam instanceof UserLoginByMessageParam){
            //是UserLoginByMessageParam的实例，开始强制转换
            UserLoginByMessageParam userLoginByMessageParam=(UserLoginByMessageParam)loginParam;
            //2-    执行验证码登陆逻辑
            loginDTO=userLoginByMessage(userLoginByMessageParam);
        }else{
            log.warn("参数错误");
            throw new ServiceException(ServiceErrorCodeConstants.USER_PARAM_ERROR);
        }
        //3-    返回值
        return loginDTO;
    }

    /**
     * 返回用户列表
     * @param identity 如果为空返回全部用户
     * @return
     */
    @Override
    public List<UserBaseInfoDTO> findUserBase(UserIdentityEnum identity) {
        //防止空指针异常
        String userIdentity=identity==null?null: identity.name();
        //1-    直接让sql判断，如果identity不存在就不加where条件  List返回的一定不为null
        List<UserDO> userDOs = userMapper.selectUsersBaseInfo(userIdentity);
        //2-    赋值并返回
        return userDOs.stream()
                .map(userDO -> {
                    UserBaseInfoDTO userBaseInfoDTO=new UserBaseInfoDTO();
                    userBaseInfoDTO.setUserName(userDO.getUserName());
                    userBaseInfoDTO.setId(userDO.getId());
                    userBaseInfoDTO.setEmail(userDO.getEmail());
                    userBaseInfoDTO.setIdentity(UserIdentityEnum.forName(userDO.getIdentity()));
                    userBaseInfoDTO.setGmtCreate(userDO.getGmtCreate());
                    userBaseInfoDTO.setGmtModified(userDO.getGmtModified());
                    userBaseInfoDTO.setPhoneNumber(userDO.getPhoneNumber().getValue());
                    return userBaseInfoDTO;
                }).collect(Collectors.toList());
    }

    /**
     * 退出逻辑处理
     */
    @Override
    public void exit(String token) {
        //1-    校验令牌
        Claims claims = jwtUtil.verify(token);
        if(claims==null){
            //令牌不合法或令牌为null：直接让前端跳转到登录页并且删除本地存储
            return;

        }else{
            //2-    令牌合法
            //3-    从令牌获取标记，存入redis
            String jti = claims.getId();
            //4-    获取有效期，设置redis有效时间
            Date expiration = claims.getExpiration();
            //毫秒为单位 但是redis封装的方法是秒，所以要除1000
            Long time=(expiration.getTime()-System.currentTimeMillis())/1000;
            if (time > 0) {
                // 将 jti 存入 Redis，并设置自动过期  value值无所谓 主要是key
                redisUtil.set("jwt_blacklist:" + jti, "invalid", time);
            }
        }
    }


    /**
     * 密码登陆处理逻辑
     * @param userLoginByPasswordParam
     * @return
     */
    public UserLoginDTO userLoginByPassword(UserLoginByPasswordParam userLoginByPasswordParam) {
        log.info("UserLoginByPasswordDTO userLoginByPassword({})",userLoginByPasswordParam);
        //1-    判断是手机号还是邮箱登陆
        UserDO userDO;
        if(RegexUtil.checkMail(userLoginByPasswordParam.getLoginName())){
            //  邮箱是否存在
            if(!checkEmail(userLoginByPasswordParam.getLoginName())){
                log.warn("邮箱不存在");
                throw new ServiceException(ServiceErrorCodeConstants.USER_EMAIL_NOT_EXIST);
            };
            //  邮箱登陆
            userDO = userMapper.selectByEmail(userLoginByPasswordParam.getLoginName());
        } else if (RegexUtil.checkPassword(userLoginByPasswordParam.getPassword())) {
            //  手机号是否存在
           if(!checkPhoneNumber(userLoginByPasswordParam.getLoginName())){
               log.warn("手机号不存在");
               throw new ServiceException(ServiceErrorCodeConstants.USER_PHONE_NUMBER_NOT_EXIST);
           };
            //  手机号登陆
            userDO = userMapper.selectByPhoneNumber(new Encrypt(userLoginByPasswordParam.getLoginName()));
        }else{
            //  非邮箱密码登陆
            log.warn("账号格式错误");
            throw new ServiceException(ServiceErrorCodeConstants.USER_ACCOUNT_ERROR);
        }

        //2-    校验用户对应的密码是否正确
        if(userDO==null){
            log.warn("用户不存在");
            throw new ServiceException(ServiceErrorCodeConstants.USER_NOT_EXIST);
        }
        if(!DecryptUtils.verifySHA256(userLoginByPasswordParam.getPassword(),userDO.getSalt(),userDO.getPassword())){
            log.warn("密码错误");
            throw new ServiceException(ServiceErrorCodeConstants.USERINFO_PASSWORD_ERROR);
        }

        //3-    校验是否有强制登陆信息，并且判断当前用户的身份信息和强制登陆信息是否相同
        if(StringUtils.hasLength(userLoginByPasswordParam.getMandatoryIdentity())
                &&!userDO.getIdentity().equalsIgnoreCase(userLoginByPasswordParam.getMandatoryIdentity())){
            log.warn("强制登录信息错误");
            throw new ServiceException(ServiceErrorCodeConstants.MANDATORY_IDENTITY_ERROR);
        }

        //4-    判断当前用户是否为管理员
        if(!userDO.getIdentity().equalsIgnoreCase(UserIdentityEnum.ADMIN.name())){
            log.warn("该用户不是管理员");
            throw new ServiceException(ServiceErrorCodeConstants.USER_NOT_ADMIN);
        }

        //5-    生成令牌
        //存入userId
        Map<String,Object>map=new HashMap<>();
        map.put("userId",userDO.getId());
        map.put("identity",userDO.getIdentity());
        String s = jwtUtil.genToken(map);
        if(s==null){
            log.warn("token生成失败");
            throw new ServiceException(ServiceErrorCodeConstants.TOKEN_CREATE_ERROR);
        }
        return new UserLoginDTO(s,UserIdentityEnum.forName(userDO.getIdentity()));
    }


    /**
     * 验证码登陆逻辑
     * @param userLoginByMessageParam
     * @return
     */
    public UserLoginDTO userLoginByMessage(UserLoginByMessageParam userLoginByMessageParam) {
        log.info("userLoginByMessage UserLoginByMessageParam({})",userLoginByMessageParam);
        //1-    校验手机号格式
        if(!RegexUtil.checkMobile(userLoginByMessageParam.getLoginMobile())){
            log.warn("手机号格式错误");
            throw new ServiceException(ServiceErrorCodeConstants.USER_PHONE_NUMBER_ERROR);
        }
        //2-    通过手机号查询用户
        UserDO userDO = userMapper.selectByPhoneNumber(new Encrypt(userLoginByMessageParam.getLoginMobile()));
        if(userDO==null){
            log.warn("用户不存在");
            throw new ServiceException(ServiceErrorCodeConstants.USER_NOT_EXIST);
        }
        //3-    校验验证码是否正确
        String verificationCode = verificationCodeService.getVerificationCode(userLoginByMessageParam.getLoginMobile());
        if(!userLoginByMessageParam.getVerificationCode().equalsIgnoreCase(verificationCode)){
            //验证码不正确
            log.warn("验证码错误");
            throw new ServiceException(ServiceErrorCodeConstants.VERIFICATION_CODE_ERROR);
        }
        //4-    校验是否有强制登陆信息，并且判断当前用户的身份信息和强制登陆信息是否相同
        if(StringUtils.hasLength(userLoginByMessageParam.getMandatoryIdentity())
                &&!userDO.getIdentity().equalsIgnoreCase(userLoginByMessageParam.getMandatoryIdentity())){
            log.warn("强制登录信息错误");
            throw new ServiceException(ServiceErrorCodeConstants.MANDATORY_IDENTITY_ERROR);
        }

        //5-    判断当前用户是否为管理员
        if(!userDO.getIdentity().equalsIgnoreCase(UserIdentityEnum.ADMIN.name())){
            log.warn("该用户不是管理员");
            throw new ServiceException(ServiceErrorCodeConstants.USER_NOT_ADMIN);
        }

        //5-    生成令牌
        //存入userId
        Map<String,Object>map=new HashMap<>();
        map.put("userId",userDO.getId());
        map.put("identity",userDO.getIdentity());
        String s = jwtUtil.genToken(map);
        if(s==null){
            log.warn("token生成失败");
            throw new ServiceException(ServiceErrorCodeConstants.TOKEN_CREATE_ERROR);
        }
        return new UserLoginDTO(s,UserIdentityEnum.forName(userDO.getIdentity()));
    }



    private void checkRegisterUserInfo(UserRegisterParam userRegisterParam) {
        //1-    校验邮箱格式
        if(!RegexUtil.checkMail(userRegisterParam.getEmail())){
            log.warn("邮箱格式错误");
            throw new ServiceException(ServiceErrorCodeConstants.USER_EMAIL_ERROR);
        }

        //2-    校验邮箱是否被使用
        if(checkEmail(userRegisterParam.getEmail())){
            log.warn("邮箱已被使用");
            throw new ServiceException(ServiceErrorCodeConstants.USER_EMAIL_USE_ERROR);
        }

        //3-    校验手机号格式
        if(!RegexUtil.checkMobile(userRegisterParam.getPhoneNumber())){
            log.warn("手机号格式错误");
            throw new ServiceException(ServiceErrorCodeConstants.USER_PHONE_NUMBER_ERROR);
        }
        //4-    校验手机号是否被使用
        if(checkPhoneNumber(userRegisterParam.getPhoneNumber())){
            log.warn("手机号已被使用");
            throw new ServiceException(ServiceErrorCodeConstants.USER_PHONE_NUMBER_USE_ERROR);
        }

        //5-    校验身份信息，是否为admin、normal
        if(UserIdentityEnum.forName(userRegisterParam.getIdentity())==null){
            log.warn("身份信息错误");
            throw new ServiceException(ServiceErrorCodeConstants.USER_IDENTITY_ERROR);
        }

        //6-    校验注册的管理员密码是否为空
        if(userRegisterParam.getIdentity().equalsIgnoreCase(UserIdentityEnum.ADMIN.name())){
            if(!StringUtils.hasLength(userRegisterParam.getPassword())){
                log.warn("管理员密码不能为空");
                throw new ServiceException(ServiceErrorCodeConstants.USER_ADMIN_PASSWORD_ERROR);
            }
        //7-    校验密码是否满足>=6位
            if(!RegexUtil.checkPassword(userRegisterParam.getPassword())){
                log.warn("密码长度不足");
                throw new ServiceException(ServiceErrorCodeConstants.USER_PASSWORD_ERROR);
            }
        }
    }

    private boolean checkPhoneNumber(String phoneNumber) {
        if(userMapper.selectByPhoneNumberCount(new Encrypt(phoneNumber))==1){
            return true;
        }
        return false;
    }

    private boolean checkEmail(String email) {
        if(userMapper.selectByEmail(email)!=null){
            return true;
        }
        return false;
    }

}
